package com.chenju.javastatic.adapter;

import com.chenju.javastatic.common.enums.EasyCode;
import com.chenju.javastatic.utils.EasyListUtils;
import com.chenju.javastatic.utils.EasyVisitUtils;
import org.objectweb.asm.*;

import java.io.IOException;
import java.util.*;
import java.util.jar.JarFile;

public class ClassVisitorAdapter extends ClassVisitor {
    public int version;
    public int access;
    public String name;
    public String signature;
    public String superName;
    public List<String> interfaces;
    public List<AnnotationVisitorAdapter> visibleAnnotations;
    public List<AnnotationVisitorAdapter> invisibleAnnotations;
    public List<FieldVisitorAdapter> fields;
    public List<MethodVisitorAdapter> methods;
    public OuterMethodVisitorAdapter outerMethod;
    public int isInterface;
    public boolean isController;
    public boolean isFeignClient;
    public boolean isAspect;
    public boolean isConfiguration;
    public List<String> mappingPaths;

    public JarFile jarFile;
    public List<OuterMethodVisitorAdapter> superClassOrInterfacesMethods;

    public ClassVisitorAdapter(JarFile jarFile) {
        this(Opcodes.ASM9, jarFile);
        if (this.getClass() != ClassVisitorAdapter.class) {
            throw new IllegalStateException();
        }
    }

    public ClassVisitorAdapter(int api, JarFile jarFile){
        super(api);
        this.methods = new ArrayList<>();
        this.fields = new ArrayList<>();
        this.interfaces = new ArrayList<>();
        this.visibleAnnotations = new ArrayList<>();
        this.isInterface = 0;
        this.superClassOrInterfacesMethods = new ArrayList<>();
        this.jarFile = jarFile;
        this.mappingPaths = new ArrayList<>();
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        this.name = name;
        if ((access & Opcodes.ACC_INTERFACE) != 0) {
            this.isInterface = 1;
        }
        this.version = version;
        this.access = access;
        this.signature = signature;
        this.interfaces = EasyListUtils.asArrayList(interfaces);
        this.superName = EasyVisitUtils.isVisitClass(superName) ? superName : null;
        this.interfaces.removeIf(interfaceName -> !EasyVisitUtils.isVisitClass(interfaceName));

        if (this.superName != null) {
            visitSuperClassOrInterfaces(this.superName, EasyCode.INVOKEEXTENDS_TYPE);
        }
        if (this.interfaces != null) {
            for (String iface : this.interfaces) {
                visitSuperClassOrInterfaces(iface, EasyCode.INVOKEIMPLEMENTS_TYPE);
            }
        }
    }

    @Override
    public void visitOuterClass(String owner, String name, String descriptor) {
        System.out.printf("owner: %s\nname: %s\ndescriptor: %s", owner, name, descriptor);
        this.outerMethod = new OuterMethodVisitorAdapter(owner, name, descriptor, EasyCode.INVOKEOUTERCLASS_TYPE);
    }

    @Override
    public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
        AnnotationVisitorAdapter annotation = new AnnotationVisitorAdapter(descriptor);
        if (visible) {
            this.visibleAnnotations = EasyListUtils.add(this.visibleAnnotations, annotation);
        } else {
            this.invisibleAnnotations = EasyListUtils.add(this.invisibleAnnotations, annotation);
        }
        return annotation;
    }

    @Override
    public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
        FieldVisitorAdapter field = new FieldVisitorAdapter(access, name, descriptor, signature, value);
        this.fields.add(field);
        return field;
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
        if (EasyVisitUtils.isVisitMethod(name)){
            OuterMethodVisitorAdapter outerVisitorAdapter = null;
            for (OuterMethodVisitorAdapter outerMethod : this.superClassOrInterfacesMethods){
                if (outerMethod.name.equals(name) && outerMethod.descriptor.equals(descriptor)){
                    outerVisitorAdapter = outerMethod;
                }
            }
            MethodVisitorAdapter method = new MethodVisitorAdapter(access, name, descriptor, signature, exceptions, this.name, outerVisitorAdapter);
            if (!this.methods.contains(method)){
                this.methods.add(method);
            }
            return method;
        }
        return null;
    }

    @Override
    public void visitEnd() {
        if (this.visibleAnnotations != null){
            for (AnnotationVisitorAdapter annotation : this.visibleAnnotations) {
                this.isController = annotation.isController || this.isController;
                this.isAspect = annotation.isAspect || this.isAspect;
                this.isConfiguration = annotation.isConfiguration || this.isConfiguration;
                this.isFeignClient = annotation.isFeignClient || this.isFeignClient;
                if (annotation.isRequestMapping){
                    this.mappingPaths = (List<String>) (annotation.valuesMap.get("path") == null ? annotation.valuesMap.get("value") : annotation.valuesMap.get("path"));
                }
            }
        }

        // 使用HashSet来存储方法签名，避免重复添加
        Set<String> methodSignatures = new HashSet<>();
        for (MethodVisitorAdapter method : methods) {
            methodSignatures.add(method.name + method.desc);
        }

        // 将需要添加的方法临时存储在一个列表中，避免ConcurrentModificationException
        List<MethodVisitorAdapter> methodsToAdd = new ArrayList<>();
        for (OuterMethodVisitorAdapter outerMethod : superClassOrInterfacesMethods) {
            String signature = outerMethod.name + outerMethod.descriptor;
            if (!methodSignatures.contains(signature)) {
                methodsToAdd.add(new MethodVisitorAdapter(
                        0,
                        outerMethod.name,
                        outerMethod.descriptor,
                        null,
                        null,
                        this.name,
                        outerMethod
                ));
                methodSignatures.add(signature); // 防止重复添加
            }
        }

        // 一次性将所有需要添加的方法添加到methods列表中
        methods.addAll(methodsToAdd);
    }

    // TODO: 对于继承有多个层级时，应逐步分析处理直到superName=null(超类为Object或者超类不在访问范围)
    public void visitSuperClassOrInterfaces(String superClassNameOrInterfaceName, String type) {
        int currentIsInterface = isInterface;
        jarFile.stream().forEach(jarEntry -> {
            if (jarEntry.getName().endsWith(superClassNameOrInterfaceName + ".class")){
                try {
                    ClassReader reader = new ClassReader(jarFile.getInputStream(jarEntry));
                    reader.accept(new ClassVisitor(Opcodes.ASM9){
                        private String name;
                        private String superName;
                        private List<String> interfaces;

                        @Override
                        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
                            this.name = name;
                            this.superName = superName;
                            this.interfaces = EasyListUtils.asArrayList(interfaces);
                        }

                        @Override
                        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
                            if (!EasyVisitUtils.isVisitMethod(name)){
                                return null;
                            }
                            String relation = null;
                            if ((EasyCode.INVOKEIMPLEMENTS_TYPE.equals(type) && currentIsInterface ==1) || EasyCode.INVOKEEXTENDS_TYPE.equals(type)){
                                relation = EasyCode.INVOKEEXTENDS_TYPE;
                            }
                            if (EasyCode.INVOKEIMPLEMENTS_TYPE.equals(type) && currentIsInterface == 0){
                                relation = EasyCode.INVOKEIMPLEMENTS_TYPE;
                            }
                            OuterMethodVisitorAdapter method = new OuterMethodVisitorAdapter(this.name, name, descriptor, relation);
                            if (!superClassOrInterfacesMethods.contains(method)){
                                superClassOrInterfacesMethods.add(method);
                            }
                            return method;
                        }

                        @Override
                        public void visitEnd() {
                            if (this.superName != null && !this.superName.equals("java/lang/Object")) {
                                visitSuperClassOrInterfaces(this.superName, EasyCode.INVOKEEXTENDS_TYPE);
                            }
                            if (this.interfaces != null) {
                                for (String iface : this.interfaces) {
                                    visitSuperClassOrInterfaces(iface, EasyCode.INVOKEIMPLEMENTS_TYPE);
                                }
                            }
                        }
                    }, 0);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}
